package cc.sunni.article.service.impl;

import cc.sunni.api.config.RabbitMQConfig;
import cc.sunni.api.config.RabbitMQDelayConfig;
import cc.sunni.api.utils.PageUtils;
import cc.sunni.api.utils.Query;
import cc.sunni.article.mapper.ArticleDao;
import cc.sunni.article.service.ArticlePortalService;
import cc.sunni.article.service.ArticleService;
import cc.sunni.common.enums.ArticleAppointType;
import cc.sunni.common.enums.ArticleReviewLevel;
import cc.sunni.common.enums.ArticleReviewStatus;
import cc.sunni.common.enums.YesOrNo;
import cc.sunni.common.exception.RRException;
import cc.sunni.common.utils.R;
import cc.sunni.common.utils.ResultEnum;
import cc.sunni.pojo.Article;
import cc.sunni.pojo.Category;
import cc.sunni.pojo.bo.NewArticleBO;
import cc.sunni.pojo.vo.ArticleDetailVO;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mongodb.client.gridfs.GridFSBucket;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.io.IOUtils;
import org.bson.types.ObjectId;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.Writer;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleDao, Article> implements ArticleService {
    @Value("${freemarker.html.article}")
    private String articlePath;
    @Autowired
    private GridFSBucket gridFSBucket;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private ArticlePortalService articlePortalService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<Article> page = this.page(
                new Query<Article>().getPage(params),
                // 拼接查询条件
                new LambdaQueryWrapper<Article>()
        );

        return new PageUtils(page);
    }

    @Override
    @Transactional
    public void createArticle(NewArticleBO newArticleBO) {
        Article article = new Article();
        BeanUtils.copyProperties(newArticleBO, article);
        article.setCategoryId(newArticleBO.getCategoryId());
        article.setArticleStatus(ArticleReviewStatus.REVIEWING.type);
        article.setCommentCounts(0);
        article.setReadCounts(0);
        article.setIsDelete(YesOrNo.NO.type);
        article.setCreateTime(new Date());
        article.setUpdateTime(new Date());

        if (article.getIsAppoint().equals(ArticleAppointType.TIMING.type)) {
            article.setPublishTime(newArticleBO.getPublishTime());
        } else if (article.getIsAppoint().equals(ArticleAppointType.IMMEDIATELY.type)) {
            article.setPublishTime(new Date());
        }

        int res = baseMapper.insert(article);
        if (res != 1) {
            throw new RRException(ResultEnum.ARTICLE_CREATE_ERROR);
        }

        // 发送延迟消息到mq，计算定时发布时间和当前时间的时间差，则为往后延迟的时间
        if (article.getIsAppoint().equals(ArticleAppointType.TIMING.type)) {
            Date endDate = newArticleBO.getPublishTime();
            Date startDate = new Date();
            int delayTimes = (int) (endDate.getTime() - startDate.getTime());

            MessagePostProcessor messagePostProcessor = message -> {
                // 设置消息的持久
                message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                // 设置消息延迟的时间，单位ms毫秒
                message.getMessageProperties().setDelay(10000);
                return message;
            };

            rabbitTemplate.convertAndSend(
                    RabbitMQDelayConfig.EXCHANGE_DELAY,
                    "publish.delay.display",
                    article.getId(),
                    messagePostProcessor);

            System.out.println("延迟消息-定时发布文章：" + new Date());
        }

        /**
         * FIXME: 我们只检测正常的词汇，非正常词汇大家课后去检测
         */
        // 通过阿里智能AI实现对文章文本的自动检测（自动审核）
//        String reviewTextResult = aliTextReviewUtils.reviewTextContent(newArticleBO.getContent());
        String reviewTextResult = ArticleReviewLevel.REVIEW.type;
        String articleId = article.getId();
        if (reviewTextResult.equalsIgnoreCase(ArticleReviewLevel.PASS.type)) {
            // 修改当前的文章，状态标记为审核通过
            this.updateArticleStatus(articleId, ArticleReviewStatus.SUCCESS.type);
        } else if (reviewTextResult.equalsIgnoreCase(ArticleReviewLevel.REVIEW.type)) {
            // 修改当前的文章，状态标记为需要人工审核
            this.updateArticleStatus(articleId, ArticleReviewStatus.WAITING_MANUAL.type);
        } else if (reviewTextResult.equalsIgnoreCase(ArticleReviewLevel.BLOCK.type)) {
            // 修改当前的文章，状态标记为审核未通过
            this.updateArticleStatus(articleId, ArticleReviewStatus.FAILED.type);
        }

    }

    @Override
    public void updateAppointToPublish() {
        baseMapper.updateAppointToPublish();
    }

    @Override
    public PageUtils queryMyArticleList(String userId, String keyword, Integer status, Date startDate, Date endDate, Integer page, Integer pageSize) {
        IPage<Article> pages = this.page(
                new Page<>(page, pageSize),
                // 拼接查询条件
                new LambdaQueryWrapper<Article>().orderByDesc(Article::getPublishTime)
                        .eq(Article::getPublishUserId, userId)
                        .like(StrUtil.isNotBlank(keyword), Article::getTitle, keyword)
                        .eq(ArticleReviewStatus.isArticleStatusValid(status), Article::getArticleStatus, status)
                        // 审核中的2种状态
                        .in(status != null && status == 12, Article::getArticleStatus, Arrays.asList(ArticleReviewStatus.REVIEWING.type, ArticleReviewStatus.WAITING_MANUAL.type))
                        .eq(Article::getIsDelete, YesOrNo.NO.type)
                        .ge(startDate != null, Article::getPublishTime, startDate == null ? null : DateUtil.beginOfDay(startDate))
                        .le(endDate != null, Article::getPublishTime, endDate == null ? null : DateUtil.endOfDay(endDate)));
        return new PageUtils(pages);
    }

    @Override
    @Transactional
    public void updateArticleStatus(String articleId, Integer pendingStatus) {
        Article pendingArticle = new Article();
        pendingArticle.setId(articleId);
        pendingArticle.setArticleStatus(pendingStatus);
        int res = baseMapper.updateById(pendingArticle);
        if (res != 1) {
            throw new RRException(ResultEnum.ARTICLE_REVIEW_ERROR);
        }

        if (pendingStatus.equals(ArticleReviewStatus.SUCCESS.type)) {
            // 审核成功，生成文章详情页静态html
            try {
                // 第一种方式直接生成到前端项目中
                // createArticleHTML(articleId);

                // 第二种方式生成到GridFS中,方便解耦
                String articleMongoId = createArticleHTMLToGridFS(articleId);
                // 存储到对应的文章，进行关联保存
                this.updateArticleToGridFS(articleId, articleMongoId);

                // 发送消息到mq队列，让消费者监听并且执行下载html
                doDownloadArticleHTMLByMQ(articleId, articleMongoId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public PageUtils queryAllArticleListAdmin(Integer status, Integer page, Integer pageSize) {
        // 审核中是机审和人审核的两个状态，所以需要单独判断
//        if (status != null && status == 12) {
//            criteria.andEqualTo("articleStatus", ArticleReviewStatus.REVIEWING.type)
//                    .orEqualTo("articleStatus", ArticleReviewStatus.WAITING_MANUAL.type);
//        }

        IPage<Article> pages = this.page(
                new Page<>(page, pageSize),
                // 拼接查询条件
                new LambdaQueryWrapper<Article>().orderByDesc(Article::getCreateTime)
                        .eq(ArticleReviewStatus.isArticleStatusValid(status), Article::getArticleStatus, status)
                        // 审核中的2种状态(机审核人审)
                        .in(status != null && status == 12, Article::getArticleStatus, Arrays.asList(ArticleReviewStatus.REVIEWING.type, ArticleReviewStatus.WAITING_MANUAL.type))
                        // 立即发布
                        .eq(Article::getIsAppoint,ArticleAppointType.IMMEDIATELY.type)
                        .eq(Article::getIsDelete, YesOrNo.NO.type));
        return new PageUtils(pages);
    }

    @Transactional
    @Override
    public void deleteArticle(String userId, String articleId) {
        LambdaUpdateWrapper<Article> updateWrapper = new LambdaUpdateWrapper<>();
        // 需要更新的字段
        updateWrapper.set(Article::getIsDelete, YesOrNo.YES.type);
        // 一定要用eq设置主键,否则就是全表更新
        updateWrapper.eq(Article::getId, articleId);
        updateWrapper.eq(Article::getPublishUserId, userId);
        int res = baseMapper.update(null, updateWrapper);

        if (res != 1) {
            throw new RRException(ResultEnum.ARTICLE_DELETE_ERROR);
        }
    }

    @Transactional
    @Override
    public void withdrawArticle(String userId, String articleId) {
        LambdaUpdateWrapper<Article> updateWrapper = new LambdaUpdateWrapper<>();
        // 需要更新的字段
        updateWrapper.set(Article::getArticleStatus, ArticleReviewStatus.WITHDRAW.type);
        // 一定要用eq设置主键,否则就是全表更新
        updateWrapper.eq(Article::getId, articleId);
        updateWrapper.eq(Article::getPublishUserId, userId);
        int res = baseMapper.update(null, updateWrapper);

        if (res != 1) {
            throw new RRException(ResultEnum.ARTICLE_WITHDRAW_ERROR);
        }
    }

    @Transactional
    @Override
    public void updateArticleToGridFS(String articleId, String articleMongoId) {
        Article pendingArticle = new Article();
        pendingArticle.setId(articleId);
        pendingArticle.setMongoFileId(articleMongoId);
        baseMapper.updateById(pendingArticle);
    }

    @Override
    public void updateArticleToPublish(String articleId) {
        Article pendingArticle = new Article();
        pendingArticle.setId(articleId);
        pendingArticle.setIsAppoint(ArticleAppointType.IMMEDIATELY.type);
        baseMapper.updateById(pendingArticle);
    }

    // 文章生成HTML保存到GridFS
    private String createArticleHTMLToGridFS(String articleId) throws Exception {
        Configuration cfg = new Configuration(Configuration.getVersion());
        String classpath = this.getClass().getResource("/").getPath();
        cfg.setDirectoryForTemplateLoading(new File(classpath + "templates"));
        Template template = cfg.getTemplate("detail.ftl", "utf-8");
        // 获得文章的详情数据
        ArticleDetailVO detailVO = getArticleDetail(articleId);
        Map<String, Object> map = new HashMap<>();
        map.put("articleDetail", detailVO);

        String htmlContent = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);

        InputStream inputStream = IOUtils.toInputStream(htmlContent);
        ObjectId fileId = gridFSBucket.uploadFromStream(detailVO.getId() + ".html", inputStream);
        return fileId.toString();
    }

    // 发起远程调用rest，获得文章详情数据
    private ArticleDetailVO getArticleDetail(String articleId) {
        String url = "http://www.imoocnews.com:8001/portal/article/detail?articleId=" + articleId;
        String responseEntity = HttpUtil.get(url);
        R r = JSONUtil.toBean(responseEntity, R.class);
        ArticleDetailVO detailVO = null;
        if ((Integer) r.get("status") == 200) {
            String detailJson = JSONUtil.toJsonStr(r.get("data"));
            detailVO = JSONUtil.toBean(detailJson, ArticleDetailVO.class);
        }
        return detailVO;
    }

    // 文章生成HTML
    private void createArticleHTML(String articleId) throws Exception {
        Configuration cfg = new Configuration(Configuration.getVersion());
        String classpath = this.getClass().getResource("/").getPath();
        cfg.setDirectoryForTemplateLoading(new File(classpath + "templates"));

        Template template = cfg.getTemplate("detail.ftl", "utf-8");

        // 获得文章的详情数据
//        ArticleDetailVO detailVO = getArticleDetail(articleId);
        ArticleDetailVO detailVO = articlePortalService.queryDetail(articleId);
        Map<String, Object> map = new HashMap<>();
        map.put("articleDetail", detailVO);

        File tempDic = new File(articlePath);
        if (!tempDic.exists()) {
            tempDic.mkdirs();
        }

        String path = articlePath + File.separator + detailVO.getId() + ".html";
        Writer out = new FileWriter(path);
        template.process(map, out);
        out.close();
    }

    private void doDownloadArticleHTMLByMQ(String articleId, String articleMongoId) {
        rabbitTemplate.convertAndSend(
                RabbitMQConfig.EXCHANGE_ARTICLE,
                RabbitMQConfig.ARTICLE_DOWNLOAD_DO,
                articleId + "," + articleMongoId);
    }
}